﻿#region Using directives

using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

using Weazel.Math;
using Weazel.Gezel.Model.Errors;

#endregion

namespace Weazel.Gezel.Model
{
  public abstract partial class Expression : IVerifiable
  {
    public abstract void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments);
  }

  public partial class LeafExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      if (ExpressionType == ExpressionType.Identifier)
      {
        VerifyIdentifierExpression(errors, ref warnings, arguments);
      }
      else if (ExpressionType == ExpressionType.Constant)
      {
        if (userSetWidthAndSign)
        {
          try
          {
            integerValue = new Integer(width, Value, signed);
          }
          catch (NotSupportedException)
          {
            ErrorMessage message = new ErrorMessage();
            message.AddString("Cannot convert value '" + Value + "' into an integer value");
            errors.Add(new VerificationError(VerificationErrorType.ExpressionInvalidIntegerValue, message));
          }
        }
        else
        {
          try
          {
            integerValue = new Integer(Value);
          }
          catch (NotSupportedException)
          {
            ErrorMessage message = new ErrorMessage();
            message.AddString("Cannot convert value '" + Value + "' into an integer value.");
            errors.Add(new VerificationError(VerificationErrorType.ExpressionInvalidIntegerValue, message));
          }
        }
      }
    }

    private void VerifyIdentifierExpression(List<VerificationError> errors, ref List<VerificationWarning> warnings, object[] arguments)
    {
      Debug.Assert(arguments.Length > 0);
      Debug.Assert(arguments[0] is Datapath);

      Datapath datapath = arguments[0] as Datapath;
      CommonEntity referenced = datapath.GetIdentifier(Value);

      if (referenced == null)
      {
        ErrorMessage message = new ErrorMessage("Unknown identifier '");
        message.AddString(Value);
        message.AddString("' found at '");
        message.AddPosition(StartPosition);

        errors.Add(new VerificationError(VerificationErrorType.ExpressionInvalidIdentifier, message));
      }
      else
        this.referencedEntity = (ITypedEntity)referenced;
    }
  }

  public partial class TernaryExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      this.Condition.Verify(ref errors, ref warnings, arguments);
      this.LeftChild.Verify(ref errors, ref warnings, arguments);
      this.RightChild.Verify(ref errors, ref warnings, arguments);
    }
  }

  public partial class BinaryExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      this.LeftChild.Verify(ref errors, ref warnings, arguments);
      this.RightChild.Verify(ref errors, ref warnings, arguments);

      if (ExpressionType == ExpressionType.Assignment)
      {
        VerifyAssignmentExpression(errors, ref warnings, arguments);
      }

      if (this.ExpressionType == ExpressionType.Div)
      {
        ErrorMessage errorMessage = new ErrorMessage();
        errorMessage.AddString("Divison (operator /) is not part of the synthetizable subset of VHDL.");
        errorMessage.AddString("You will not be able to synthetize this design.");

        VerificationWarning warning = new VerificationWarning(errorMessage);

        warnings.Add(warning);
      }

      if (this.ExpressionType == ExpressionType.Assignment && this.rightChild.ExpressionType == ExpressionType.Assignment)
      {
          ErrorMessage errorMessage = new ErrorMessage();
          errorMessage.AddString("Only one assignment allowed per statement at ");
          errorMessage.AddPosition(StartPosition);

          VerificationError error = new VerificationError(VerificationErrorType.DoubleAssignment, errorMessage);

          errors.Add(error);
      }
    }

    /// <summary>
    /// Verifies an assignment expression by checking
    /// that it appears in a signalflowgraph and that
    /// its left side is assignable (Signal, Register
    /// or OutPort). Also, references to definers
    /// are set up during verification.
    /// </summary>
    /// <param name="errors">the list of accumulated errors</param>
    /// <param name="arguments">custom arguments</param>
    private void VerifyAssignmentExpression(List<VerificationError> errors, ref List<VerificationWarning> warnings, object[] arguments)
    {
      if (arguments[1] == null || !(arguments[1] is SignalFlowGraph))
      {
        errors.Add(new VerificationError(VerificationErrorType.ExpressionAssignmentOutsideSfg));
        // errors.Add(new VerificationError(this.Name, VerificationErrorType.ExpressionAssignmentOutsideSfg));
        return;
      }
      SignalFlowGraph sfg = arguments[1] as SignalFlowGraph;

      LeafExpression lExpr = LeftChild as LeafExpression;
      if (lExpr == null || lExpr.ExpressionType != ExpressionType.Identifier)
      {
        ErrorMessage message = new ErrorMessage();
        message.AddString("Invalid left hand side of assignment expression found at ");
        message.AddPosition(StartPosition);

        errors.Add(new VerificationError(VerificationErrorType.ExpressionLeftHandSideNotAssignable, message));
      }
      else
      {
        ITypedEntity entity = lExpr.ReferencedEntity;

        if (entity is Signal)
        {
          Signal signal = entity as Signal;
          signal.Definers.Add(RightChild);
          signal.DefiningSignalFlowGraphs[RightChild] = sfg;
        }
        else if (entity is Register)
        {
          Register reg = entity as Register;
          reg.Definers.Add(RightChild);
          reg.DefiningSignalFlowGraphs[RightChild] = sfg;
        }
        else if (entity is OutPort)
        {
          OutPort outPort = entity as OutPort;
          outPort.Definers.Add(RightChild);
          outPort.DefiningSignalFlowGraphs[RightChild] = sfg;
        }
        else if (entity is InPort)
        {
          ErrorMessage message = new ErrorMessage("Cannot assign port (direction in) at '");
          message.AddPosition(StartPosition);
          message.AddString("'");

          errors.Add(new VerificationError(VerificationErrorType.ExpressionLeftHandSideNotAssignable, message));
        }
        else if (entity == null)
        {
          // this error will be reported twice. Here and when verifying the left leaf expression.

          ErrorMessage message = new ErrorMessage("Unknown identifier '");
          message.AddString(lExpr.Value);
          message.AddString("' found at '");
          message.AddPosition(StartPosition);
          message.AddString("'");

          errors.Add(new VerificationError(VerificationErrorType.UnknownIdentifier, message));
        }
        else
        {
          throw new ApplicationException("internal error");
        }
      }
    }
  }

  public partial class UnaryExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      this.Child.Verify(ref errors, ref warnings, arguments);
    }
  }

  public partial class IndexExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
        if (arguments.Length > 0 && arguments[0] is Datapath)
        {
            Datapath parent = (Datapath)arguments[0];

            /*
             * 
             * PBS: disables check, Name is not same as lookup
             * TODO
             * 
            if (!parent.LookupTables.ContainsKey(Name))
            {                
                ErrorMessage message = new ErrorMessage();
                message.AddString("Indexing () applied to non-lookup table signal at ");
                message.AddPosition(this.StartPosition);

                VerificationError error = 
                    new VerificationError(VerificationErrorType.LookupIndexError, message);

                errors.Add(error);
            }
            */

        }               

      this.IndexExpr.Verify(ref errors, ref warnings, arguments);
      this.Identifier.Verify(ref errors, ref warnings, arguments);
    }
  }

  public partial class ListExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      foreach (Expression expr in List)
        expr.Verify(ref errors, ref warnings, arguments);
    }
  }

  public partial class CastExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      this.Child.Verify(ref errors, ref warnings, arguments);
    }
  }

  public partial class RangeExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      this.Child.Verify(ref errors, ref warnings, arguments);
    }
  }

  public partial class DirectiveExpression
  {
    /// <summary>
    /// 
    /// </summary>
    /// <param name="errors"></param>
    /// <param name="arguments">optional custom verification arguments</param>
    public override void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
    {
      if (this.ExpressionType == ExpressionType.DisplayDirective)
        foreach (Expression expr in this.Arguments)
          expr.Verify(ref errors, ref warnings, arguments);
    }
  }
}
